/**
* @file RenderEngine.cpp
* @author Marcus Antonsson
* @version 2009-12-17
*
* <code>RenderEngine</code> is a (singleton) class used to render graphics 
* for the Putin RTS game engine
*
* <p><b>NOTE:</b> resources (meshes, materials etc) need to be placed in the 
* resurces folder if they are to be parsed and therefor be able to be used
**/

#include "RenderEngine.h"
#include <limits> // for float infinity
#include <typeinfo>
#include <set>

namespace Putin {

/**
* Class constructor
*/
RenderEngine::RenderEngine( void ) : Singleton<RenderEngine>() 
{
	try
	{
		mRoot = new Ogre::Root( CFG_FILE_PLUGINS, 
				USER_SETTINGS_STR, LOG_FILE );	
	}
	catch( Ogre::Exception& e ) 
	{
		std::cout << "An exception occurred while reading config " 
				<< e.getFile() << std::endl;
	}

	mSkybox = "";
}

/**
* Class destructor
*/
RenderEngine::~RenderEngine( void )
{
	delete mRoot;
}

/**
* Initializes what is needed from the Ogre framework, must be called 
* before anything else.
*/
void RenderEngine::init( void ) 
{
	try
	{
		//If not previously choosen, the user gets prompted if they prefer 
		//DirectX or OpenGL. Will hardcode in OpenGL here later on so we dont
		//have to make double sets of shader programs
		
		if ( !mRoot->restoreConfig() ) {
			mRoot->showConfigDialog();
		}
		
		/*
		Ogre::RenderSystem* renderSystem = mRoot->getRenderSystemByName( "Direct3D9 Rendering Subsystem" );
                                            // or use "OpenGL Rendering Subsystem"
        mRoot->setRenderSystem( renderSystem );
        renderSystem->setConfigOption("Full Screen", "No");
        renderSystem->setConfigOption("Video Mode", "640 x 480 @ 16-bit colour");
		*/
		mWindow = mRoot->initialise( true, "PutinApplication" );

		//Load and parse resources
		Ogre::ConfigFile cf;
		cf.load( CFG_FILE_RESOURCES );
		Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
		
		while ( seci.hasMoreElements() ) {
			Ogre::String secName = seci.peekNextKey();
			Ogre::ConfigFile::SettingsMultiMap* settings = seci.getNext();
			for ( Ogre::ConfigFile::SettingsMultiMap::iterator i = settings->begin();
					i != settings->end(); i++) {
				
				Ogre::String typeName = i->first;
				Ogre::String archName = i->second;
				
				Ogre::ResourceGroupManager::getSingleton().addResourceLocation( 
						archName, typeName, secName );
			}
		}
		//Font creation
		Ogre::FontManager& fontManager = Ogre::FontManager::getSingleton();
		Ogre::ResourcePtr font = fontManager.create( "Snuskpk", "General" );
		font->setParameter( "type", "truetype" );
		font->setParameter( "source", "Snuskpk.ttf" );
		font->setParameter( "size", "18" );
		font->setParameter( "resolution", "96" );
		font->load();
	}
	catch( Ogre::Exception& e )
	{
		std::cout << "An exception occurred while reading file " 
				<< e.getFile() << std::endl;
	}

	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
	
	mSceneManager = mRoot->createSceneManager( Ogre::ST_EXTERIOR_CLOSE, 
			"sceneManager" );
	
	mSceneManager->getRootSceneNode()->setCustomParameter( 1, Ogre::Vector4( 0.0f, 0.0f, 0.0f, 0.0f ) );

	//got some silly presets here now, gonna see what we need from them
	//and then change the code...
	mSceneManager->setShadowTechnique( Ogre::SHADOWTYPE_STENCIL_MODULATIVE );
	
	mSceneManager->setShadowTextureSize( 2048 );
	mSceneManager->setShadowColour( Ogre::ColourValue(0.6, 0.6, 0.6 ) );
    mSceneManager->setShadowFarDistance( 800 );
	mSceneManager->setAmbientLight( Ogre::ColourValue( 0.5, 0.5, 0.5 ) );
	
	mSunLight = mSceneManager->createLight( "SunLight" );
	mSunLight->setType( Ogre::Light::LT_DIRECTIONAL );
	mSunLight->setDirection( Ogre::Vector3( 0.35f, -0.3f, 0.25f ) );
	mSunLight->setDiffuseColour( Ogre::ColourValue::White );
	mSunLight->setSpecularColour( Ogre::ColourValue::White );

	if( mSkybox != "" ) // wrapper
	{
		mSceneManager->setSkyBox(true,mSkybox,5000,false);
	}

	mCamera = mSceneManager->createCamera( "camera" );
	
	setCameraClipDistances( 5.0f, 5000.0f );
	setCameraPosition( Vec3f( 0.0f, 0.0f, 0.0f ) );
	setCameraLookAt( Vec3f( 0.0f, 0.0f, 0.0f ) );

	mViewport = mWindow->addViewport( mCamera );
	mViewport->setBackgroundColour( Ogre::ColourValue( 0.0f, 0.0f, 0.0f ) );

	createTextOverlay();
}

/**
* Loads a map config/heightmap that is rendered.
*
* @param fileName name of the map config
*/
void RenderEngine::loadMap( const std::string& fileName ) 
{
	mSceneManager->setWorldGeometry( (Ogre::String&) fileName );
}
/**
* Updates renderable objects render data passed from the game logic.
* This is the main update algorithm for the graphics engine
*
* @param units vector of the units to have render data updated
*/
void RenderEngine::updateUnits( std::vector< RenderableUnit* > units ) 
{
	for ( unsigned int i = 0; i < units.size(); i++ ) {

		//scenemanager throws an exception if entity isn't found
		//TODO: implement a better way that doesn't use exceptions in this way
		bool newUnit = false; 
		Ogre::Entity* currentEntity;
		if( mLoaded.find(units[i]->getId()) == mLoaded.end() )
		{
			newUnit = true;
			mLoaded.insert(units[i]->getId());
		}
		else
		{
			currentEntity =  mSceneManager->getEntity( units[i]->getId() );
		}
		
		Ogre::SceneNode* node;
		Ogre::SceneNode* childNode = NULL ; // used for towers and stuff


		//Create new entity if unit is new
		if ( newUnit ) {
			Ogre::Entity* entity = mSceneManager->createEntity( units[i]->getId(), 
					units[i]->getMesh() );
			entity->setMaterialName( units[i]->getMaterial()  );
			entity->setCastShadows( units[i]->isShadowcaster());
			node = mSceneManager->getRootSceneNode()->createChildSceneNode();
			node->attachObject( entity );
		
			// --- edited by PETTER ---
			
			if( units[i]->hasChild() )
			{
				// use the same ID number, but add "c" infront
				Ogre::Entity* childEnt = mSceneManager->createEntity( 
												std::string("c")+units[i]->getId(),
												units[i]->getChildMesh());
				childEnt->setCastShadows( units[i]->isShadowcaster());
				childEnt->setMaterialName(units[i]->getChildMaterial());
				childNode = node->createChildSceneNode( units[i]->getId() + "cNode" );
				childNode->attachObject(childEnt);
			}


			// --- END ------- 

			float scale = units[i]->getScale();
			node->setScale( Ogre::Vector3( scale, scale, scale ) );

		} else {
			node = currentEntity->getParentSceneNode();
			if( units[i]->hasChild() ){
				childNode = static_cast< Ogre::SceneNode* >( node->getChild( units[i]->getId() + "cNode" ) ); // edited by Petter
			}
		}
		
		//converting to Ogre defined vectors and update position
		Vec3f location = units[i]->getPosition();
		node->setPosition( Ogre::Vector3( location.x, location.y, location.z ) );
		/*
		//Quaternion math: Prob. better to get this to work.. Does not right now
		//convert and rotate to facing direction
		Vec3f tempFacing = units[i]->getFacingDirection();
		Ogre::Vector3 facingOrientation = node->getOrientation() * Ogre::Vector3::NEGATIVE_UNIT_Z;
		facingOrientation.normalise();
		facingOrientation.y = 0.0f;
		Ogre::Vector3 facing( tempFacing.x , 0.0f , tempFacing.z );
		facing.normalise();
		Ogre::Quaternion facingQuat = facingOrientation.getRotationTo( facing );  

		//convert and rotate normal after surface
		Vec3f tempNormal = units[i]->getUpVector();
		Ogre::Vector3 normalOrientation = node->getOrientation() * Ogre::Vector3::UNIT_Y;
		normalOrientation.normalise();
		Ogre::Vector3 normal( tempNormal.x, tempNormal.y, tempNormal.z );
		normal.normalise();
		Ogre::Quaternion normalQuat = normalOrientation.getRotationTo( normal );  
		node->rotate( facingQuat + normalQuat );
*/
		// Child rotation - WORKS
		if( units[i]->hasChild() ) // Added by Petter
		{
			// Per axis rotation. by radians. Not so intiutive though..
			// this solution might suffer from gimbal lock though...
			Vec3f childTempFacing = units[i]->getChildDirection().normalized();
			childNode->resetOrientation();
			childNode->rotate(Ogre::Vector3::UNIT_X,Ogre::Radian( units[i]->getChildDirection().x ));
			childNode->rotate(Ogre::Vector3::UNIT_Y,Ogre::Radian( units[i]->getChildDirection().y ));
			childNode->rotate(Ogre::Vector3::UNIT_Z,Ogre::Radian( units[i]->getChildDirection().z ));
		}
		

		Vec3f tempFacing = units[i]->getFacingDirection();
		Vec3f tempNormal = units[i]->getUpVector();
		Ogre::Vector3 normal( tempNormal.x, tempNormal.y, tempNormal.z );
		Ogre::Vector3 rotationalAxis= normal.crossProduct( Ogre::Vector3::UNIT_Y );

		//Ogre::Radian r = Ogre::Math::ATan2(Ogre::Math::Sqrt(tempNormal.z*tempNormal.z+tempNormal.x*tempNormal.x),tempNormal.y);
		Ogre::Radian r = Ogre::Math::ACos( normal.dotProduct(Ogre::Vector3::UNIT_Y) ) ;
		node->resetOrientation();
		rotationalAxis.normalise();
		node->rotate(rotationalAxis, -r, Ogre::Node::TS_WORLD);

		r = Ogre::Math::ATan2(tempFacing.z,tempFacing.x)+Ogre::Radian::Radian(Ogre::Math::PI/2);//facing.angleBetween(Ogre::Vector3::NEGATIVE_UNIT_Z);
		node->rotate(Ogre::Vector3::NEGATIVE_UNIT_Y, r, Ogre::Node::TS_LOCAL);
	}
}
/**
* Sets the direction of the sunlight
*
* @param direction position for the sun to look at
*/
void RenderEngine::setSunDirection( Vec3f direction )
{
	mSunLight->setDirection( Ogre::Vector3( direction.x, direction.y, direction.z ) );
}

/**
* Adds a particle effect to a unit
*
* @param unitId Id of the unit to add effect to
* @param effectName The name of the particle effect
* @param toChild If the effect is to be added to the child
*/
void RenderEngine::addParticleEffect( std::string unitId, std::string effectName, bool toChild )
{
	Ogre::Entity* currentEntity =  mSceneManager->getEntity( unitId );
	Ogre::SceneNode* node = currentEntity->getParentSceneNode();
	Ogre::String pName = effectName + unitId;

	//Add parent particle effects	
	if ( toChild ) {	
		//Remove for recreation if it exists
		pName += "C";
		if( mParticleEffects[unitId].find( pName ) != mParticleEffects[unitId].end() ) {
			removeParticleEffect( unitId, effectName, toChild );
		}
		//set node to units child node
		node = static_cast< Ogre::SceneNode* >( node->getChild( unitId + "cNode" ) );
	} else {
		if( mParticleEffects[unitId].find( pName ) != mParticleEffects[unitId].end() ) {
			removeParticleEffect( unitId, effectName, toChild );	
		}
	}
	//create system
	Ogre::ParticleSystem* pSystem = mSceneManager->createParticleSystem( 
			pName, (Ogre::String) effectName );
	Ogre::SceneNode* particleNode = node->createChildSceneNode( pName +	"node" );
	particleNode->attachObject( pSystem );
	mParticleEffects[unitId].insert( pName );

}

/**
* Adds a particle effect to a position
*
* @param name Id to give to the particle effect
* @param effectName The name of the particle effect
* @param worldLocation position to place the effect
*/
void RenderEngine::addParticleEffect( std::string name, std::string effectName, Vec3f worldLocation )
{
	Ogre::SceneNode* node = mSceneManager->getRootSceneNode()->createChildSceneNode( "freeParticleNode" + name );
	Ogre::ParticleSystem* pSystem = mSceneManager->createParticleSystem( 
			"freeParticle" + name, (Ogre::String) effectName );
	node->setPosition( Ogre::Vector3( worldLocation.x, worldLocation.y, worldLocation.z ) ); 
	node->attachObject( pSystem );
}
/**
* Removes a particle effect to a unit
*
* @param unitId Id of the unit to remove effect from
* @param effectName The name of the particle effect
* @param toChild If the effect is to be removed from the child
*/
void RenderEngine::removeParticleEffect( std::string unitId, std::string effectName, bool fromChild )
{
	Ogre::Entity* currentEntity =  mSceneManager->getEntity( unitId );
	Ogre::SceneNode* node = currentEntity->getParentSceneNode();
	Ogre::String pName = effectName + unitId;

	if ( fromChild ) {
		pName += "C";
		node = static_cast< Ogre::SceneNode* >( node->getChild( unitId + "cNode" ) );
	}
	mSceneManager->destroyParticleSystem( pName );
	node->removeAndDestroyChild( pName + "node" );	
	mParticleEffects[unitId].erase( pName );

}

/**
* Removes a particle effect to a position, not fully tested and needs work
*
* @param name Id of the particle effect to remove
* @param effectName The name of the particle effect
* @param worldLocation position to remove the effect from
*/
void RenderEngine::removeParticleEffect( std::string name, std::string effectName, Vec3f worldLocation )
{
	Ogre::SceneNode* node = static_cast< Ogre::SceneNode* >( 
			mSceneManager->getRootSceneNode()->getChild( "freeParticleNode" + name ) );
	node->removeAllChildren();
	mSceneManager->destroyParticleSystem( "freeParticle" + name );
	mSceneManager->destroySceneNode( node->getName() );
}
/**
* Removes a unit from the scene
*
* @param unit The unit that should be removed
*/
void RenderEngine::removeUnit( RenderableUnit* unit )
{
	mLoaded.erase(unit->getId());
	Ogre::Entity* entity =  mSceneManager->getEntity( unit->getId() );
	Ogre::SceneNode* node = entity->getParentSceneNode();
	node->removeAllChildren();
	mSceneManager->destroyEntity( entity );
	mSceneManager->destroySceneNode( node->getName() );
}
/**
* Starts the rendering loop.
*/
void RenderEngine::startRendering( void )
{
	mRoot->startRendering();
}

/**
* Render one frame.
*/
void RenderEngine::renderOneFrame( void )
{
	Ogre::WindowEventUtilities::messagePump();
	mRoot->renderOneFrame();
}

/**
* Sets the position where the camera should look.
*
* @param position The position where the camera should look
*/
void RenderEngine::setCameraLookAt( Vec3f position )
{
	mCamera->lookAt( Ogre::Vector3( position.x, position.y, position.z) );
	
}

/**
* Sets the camera position.
*
* @param position The position where the camera should look
*/
void RenderEngine::setCameraPosition( Vec3f position )
{
	mCamera->setPosition( Ogre::Vector3( position.x, position.y, position.z) );
	
}

/**
* Sets the camera clipping distances
*
* @param nearClip How close the near clip plane should be positioned
* @param farClip How far away the far clip plane should be positioned
*/
void RenderEngine::setCameraClipDistances( float nearClip, float farClip )
{
	mCamera->setFarClipDistance( farClip ); 
	mCamera->setNearClipDistance( nearClip );
}

/**
* Returns the id of the renderable unit that was clicked
* returns 0 if none.
*
* @param mousePosition where the mouse clicked on screen. 
* The x and y position at which the ray should intersect 
* the viewport has to be normalised screen coordinates [0,1]
*/
int RenderEngine::renderableUnitQuery( Vec2f mousePosition )
{
	// Setup the ray scene query, use mouse position
	Ogre::Ray mouseRay = mCamera->getCameraToViewportRay( 
			mousePosition.x, mousePosition.y );
	
	mRaySceneQuery = mSceneManager->createRayQuery( mouseRay );
	mRaySceneQuery->setSortByDistance( true );
	mRaySceneQuery->setQueryTypeMask(Ogre::SceneManager::ENTITY_TYPE_MASK); // filter to only entities, ignore ground
	mRaySceneQuery->setRay( mouseRay );

	Ogre::RaySceneQueryResult& result = mRaySceneQuery->execute();
	Ogre::RaySceneQueryResult::iterator itr = result.begin();
	
	int ret = 0; // Moved the destroyQuery down a bit, elsewhere, it crashed / Petter
    if ( itr != result.end() && itr->movable ) {
		Ogre::String name = itr->movable->getName();
		
		if( name[0] == 'c' ) 
		{
			name = name.substr(1);
		}	

		ret= Ogre::StringConverter::parseInt( name );
    }
	else {
		ret = 0;
	}
	mSceneManager->destroyQuery( mRaySceneQuery );
	return ret;
}

/**
* Returns the world position of the clicked pixel
*
* @param mousePosition where the mouse clicked on screen. 
* The x and y position at which the ray should intersect 
* the viewport has to be normalised screen coordinates [0,1]

*/
Vec3f RenderEngine::worldPositionQuery( Vec2f mousePosition )
{
	// Setup the ray scene query, use mouse position
	Ogre::Ray mouseRay = mCamera->getCameraToViewportRay( 
			mousePosition.x, mousePosition.y );
	
	mRaySceneQuery = mSceneManager->createRayQuery( mouseRay );
	mRaySceneQuery->setRay( mouseRay );

	Ogre::RaySceneQueryResult& result = mRaySceneQuery->execute();
	Ogre::RaySceneQueryResult::iterator itr = result.begin();
	
	float inf = std::numeric_limits<float>::infinity();
	Vec3f ret = Vec3f(inf,inf,inf);

    if ( itr != result.end() && itr->worldFragment ) {
		Ogre::Vector3 worldPosition = itr->worldFragment->singleIntersection;
		//convert before returning
		ret = Vec3f( worldPosition.x, worldPosition.z, worldPosition.y );
	} else {
		//TODO: CAST SERIOUS ERROR
		// What to return if the user clicks the skybox?
	}
	mSceneManager->destroyQuery( mRaySceneQuery );
	return ret;
	
}

/**
* Shows the units bounding box when rendering
*
* @param unit The unit which bounding box you want to toggle
* @param showBounding True to show, false to hide.
*/
void RenderEngine::showUnitBoundingBox( RenderableUnit* unit, bool showBounding )
{
	try
	{
	Ogre::Entity* entity =  mSceneManager->getEntity( (Ogre::String) unit->getId() );
	entity->getParentSceneNode()->showBoundingBox( showBounding );
	}
	catch ( Ogre::Exception& e )
	{

	}
}
 /**
* Needed for mouse positioning
*/
HWND RenderEngine::getWinHandle()
{
	HWND hWnd = NULL;
	mWindow->getCustomAttribute("WINDOW",&hWnd);
	return hWnd;
}

/**
* Needed for mouse positioning
*/
Vec2i RenderEngine::getWinSize()
{
	unsigned int width = mWindow->getWidth();
	unsigned int height = mWindow->getHeight();
	return Vec2i(width,height);
}

/**
* getter for a units bounding box
*
* @param unitId The unit which bounding box you want returned
*/
Ogre::AxisAlignedBox RenderEngine::getUnitAABB( std::string unitId )
{
	return mSceneManager->getEntity( unitId )->getBoundingBox();
}

/**
* getter for a units AABB half size
*
* @param unitId The unit which bounding box half size you want returned
*/
Vec3f RenderEngine::getUnitAABBhalfSize( std::string unitId )
{
	Ogre::AxisAlignedBox box = mSceneManager->getEntity( unitId )->getBoundingBox();
	return Vec3f( box.getHalfSize().x, box.getHalfSize().z, box.getHalfSize().y);
}

/**
* getter for a units OBB half size
*
* @param unitId The unit which bounding box half size you want returned
*/
Vec3f RenderEngine::getUnitOBBhalfSize( std::string unitId )
{
	Ogre::AxisAlignedBox box = mSceneManager->getEntity( unitId )->getBoundingBox();
	Ogre::Matrix4 transform = mSceneManager->getEntity( unitId 
			)->getParentSceneNode()->_getFullTransform();
	box.transformAffine( transform );
	return Vec3f( box.getHalfSize().x, box.getHalfSize().z, box.getHalfSize().y);
}

/**
* initiates the text overlay used when debugging
*/
void RenderEngine::createTextOverlay( void )
{
	// get the overlay manager
	Ogre::OverlayManager& overlayManager = Ogre::OverlayManager::getSingleton();
	
	// Create a panel
	Ogre::OverlayContainer* panel = static_cast< Ogre::OverlayContainer* >(
		   overlayManager.createOverlayElement( "Panel", "PutinPanel" ) );
	panel->setMetricsMode( Ogre::GMM_PIXELS );
	panel->setPosition( 10, 10 );
	panel->setDimensions( 300, 120 );

	// Create a text area
	Ogre::TextAreaOverlayElement* textArea = static_cast< Ogre::TextAreaOverlayElement* >(
			overlayManager.createOverlayElement( "TextArea", "Putin propaganda debug manager") );
	textArea->setMetricsMode( Ogre::GMM_PIXELS );
	textArea->setPosition( 0, 0 );
	textArea->setDimensions( 300, 120 );
	textArea->setCharHeight( 18);
	textArea->setFontName( "Snuskpk" );

	// Create an overlay, and add the panel
	Ogre::Overlay* overlay = overlayManager.create( "PutinOverlay" );
	overlay->add2D( panel );
	// Add the text area to the panel
	panel->addChild( textArea );
}

/**
* What to show in the text overlay
* @param text text to be shown
*/
void RenderEngine::setTextOverlay( std::string text )
{
	Ogre::OverlayManager& overlayManager = Ogre::OverlayManager::getSingleton();
	Ogre::TextAreaOverlayElement* textArea = static_cast< Ogre::TextAreaOverlayElement* >(
			overlayManager.getOverlayElement( "Putin propaganda debug manager" ) );
	textArea->setCaption( text ); 
}

/**
* Used to make the text overlay visible or hide it
* @param show boolean if to show or hide
*/
void RenderEngine::showTextOverlay(bool show)
{
	Ogre::OverlayManager& overlayManager = Ogre::OverlayManager::getSingleton();
	Ogre::Overlay* overlay = overlayManager.getByName( "PutinOverlay" );
	if (show) {
		overlay->show();
	} else {
		overlay->hide();
	}

}
/**
* Generates a Image heightmap from a Glest Map instance
* @param map The map instance to generate the heightmap from
*/
void RenderEngine::MapTool::generateHeightmapToImage( Map* map )
{
	try 
	{
		const int HEIGHT_INTENSITY = 6;
		// Bytes needed for one channel
		const unsigned int BYTES = 1;

		Ogre::Image image;
		Ogre::PixelFormat format = Ogre::PF_BYTE_L;
		Ogre::uchar* data = new Ogre::uchar[ map->getW() * map->getH() * BYTES ];
		Ogre::uchar* pos = data;

		// Fill data array with info
		for ( int i = 0; i < map->getH(); i++ ) {
			for ( int j = 0; j < map->getW(); j++ ) {
				Ogre::uint val = Ogre::uint( HEIGHT_INTENSITY * map->getHeight( j, i ) );
				std::memcpy( pos, &val, BYTES );
				pos += BYTES;
			}
		}
		// Write info
		image.loadDynamicImage( data, map->getW(), map->getH(), 1, format, true );
		image.save( "../putinresources/maps/heightmap/putin.png" );
	}
	catch ( Ogre::Exception& e )
	{
		std::cout << "An exception occurred while reading file " 
			<< e.getFile() << "in MapToHeightmap::generateHeightmapToImage()" << std::endl;
	}
}

}// End namespace Putin